const ts = require('typescript')

/**
 * @type {import('typescript').TransformerFactory<import('typescript').SourceFile>}
 */
module.exports.loadableTransformer = function loadableTransformer(context) {
  /**
   * @type {import('typescript').Visitor}
   */
  const visitor = (node) => {
    if (!ts.isVariableStatement(node)) {
      return ts.visitEachChild(node, visitor, context)
    }

    const declarations = node.declarationList.declarations

    if (declarations && Array.isArray(declarations)) {
      /**
       * @type {import('typescript').VariableDeclaration}
       */
      const declaration = declarations.find(
        (d) =>
          d.initializer &&
          ts.isCallExpression(d.initializer) &&
          d.initializer.expression &&
          ts.isIdentifier(d.initializer.expression) &&
          d.initializer.expression.text === 'Loadable',
      )

      if (!declaration) {
        return node
      }

      /**
       * @type {import('typescript').CallExpression}
       */
      const initializer = declaration.initializer

      if (
        initializer.arguments.length !== 1 ||
        initializer.arguments[0].kind !== ts.SyntaxKind.ObjectLiteralExpression
      ) {
        return node
      }

      /**
       * @type {import('typescript').ObjectLiteralExpression}
       */
      const loadableOpts = initializer.arguments[0]

      const properties = loadableOpts.properties

      const propertiesMap = {}
      properties.forEach((p) => (propertiesMap[p.name.text] = p))

      if (
        propertiesMap.webpack ||
        propertiesMap.loader.kind !== ts.SyntaxKind.PropertyAssignment ||
        propertiesMap.loader.initializer.kind !== ts.SyntaxKind.ArrowFunction
      ) {
        return node
      }

      /**
       * @type {import('typescript').StringLiteral}
       */
      let importPath = null

      if (propertiesMap.loader.initializer.body.expression.kind === ts.SyntaxKind.ImportKeyword) {
        importPath = propertiesMap.loader.initializer.body.arguments[0]
      } else if (ts.isPropertyAccessExpression(propertiesMap.loader.initializer.body.expression)) {
        importPath = propertiesMap.loader.initializer.body.expression.expression.arguments[0]
      }

      if (!importPath) {
        return node
      }

      const fullText = importPath.getFullText()

      const matches = fullText.match(
        new RegExp(`\\/\\*\\s*webpackChunkName:\\s*"(.+)"\\s*\\*\\/\\s*'${importPath.text}'`),
      )

      if (!matches || !matches[1]) {
        const sourceFile = importPath.getSourceFile()
        const { line, character } = sourceFile.getLineAndCharacterOfPosition(importPath.pos)

        throw new Error(`You must specific a webpackChunkName for dynamic import. L${line} ${character}`)
      }

      const newProperties = [
        ...properties,
        context.factory.createPropertyAssignment(
          'webpack',
          context.factory.createArrowFunction(
            [],
            [],
            [],
            undefined,
            context.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
            context.factory.createArrayLiteral([
              context.factory.createCall(
                context.factory.createPropertyAccess(
                  context.factory.createIdentifier('require'),
                  context.factory.createIdentifier('resolveWeak'),
                ),
                [],
                [importPath],
              ),
            ]),
          ),
        ),
        context.factory.createPropertyAssignment(
          'modules',
          context.factory.createArrayLiteral([context.factory.createStringLiteral(matches[1])]),
        ),
      ]

      return context.factory.updateVariableStatement(
        node,
        node.modifiers,
        context.factory.createVariableDeclarationList([
          context.factory.createVariableDeclaration(
            declaration.name,
            undefined,
            context.factory.createCall(
              context.factory.createIdentifier('Loadable'),
              [],
              [context.factory.createObjectLiteral(newProperties)],
            ),
          ),
        ]),
      )
    }
  }

  return (node) => ts.visitNode(node, visitor)
}
